goog.provide('dkpoints.raid.parsedview');

goog.require('goog.dom');
goog.require('goog.dom.classes');
goog.require('goog.fx.DragDrop');
goog.require('goog.fx.DragDropGroup');
goog.require('soy');

goog.require('dkpoints.utils');
goog.require('dkpoints.status');
goog.require('dkpoints.raid.templates');
goog.require('dkpoints.rpc');
goog.require('dkpoints.rpc.commands.GetTierList');
goog.require('dkpoints.rpc.commands.GetItemList');

/**
 * DragDropGroup to hold player names as drag sources from the Raid Member list.
 * @type {goog.fx.DragDropGroup}
 */
dkpoints.raid.parsedview.playerGroup = null;

/**
 * DragDropGroup to point to 'Looted by:' fields as drop targets in the Raid Loot list.
 * @type {goog.fx.DragDropGroup}
 */
dkpoints.raid.parsedview.looterGroup = null;

/**
 * An array of tier names.
 * @type {Array}
 * @public
 */
dkpoints.raid.tierList = null;

/**
 * Display a raid object to the user.  Changes will not be saved unless the saveCallback is called.
 * @param {!dkpoints.raid.Raid} raidOb Cloned Raid object to display.
 * @param {boolean} editable Show the editing UI on top of the viewing UI.
 * @param {function(!dkpoints.raid.Raid)=} saveCallback Callback passed in when editable.
 */
dkpoints.raid.parsedview.show = function(raidOb, editable, saveCallback) {
  var me = dkpoints.raid.parsedview;
  me.startTime = Date.now();
  
  if(! goog.isDefAndNotNull(me.raid)) {
    me.raid = raidOb;
  }
  else {
    // If I already have a raid object, see if it's the same as the one I was just passed.
    var curDate = me.raid["info"]["date"],
        newDate = raidOb["info"]["date"];
        
    // If they're not the same raid object, replace.
    // If they ARE the same object, don't overwrite my changes.
    if(curDate !== newDate) {
      me.raid = raidOb;
    }
    else {
      raidOb = me.raid;
    }
  }
  
  // Used by gotTierList in order to know when to render the <select> boxes.
  me.rendered = false;
  
  // Check if we have a tier list yet.
  if(goog.isDef(me.tierList)) {
    
    // Yep.  Do we have this raid's instance tier?
    if(! goog.isDef(me.raid.instanceTier)) {
      
      // No, we don't.  Get this instance's tier.
      // TODO: Build the getInstanceTier command object.
    }
    // else:  We have everything we need.  Don't do anything.
  }
  else {
    // We don't have the tier list.
    var cmdGetTierList = new dkpoints.rpc.commands.GetTierList(me.gotTierList, raidOb["info"]["zone"]);
    dkpoints.rpc.execute(cmdGetTierList);
  }
  
  if( (editable && (! saveCallback)) || // Editable flag was true but no callback provided.
      ((! editable) &&  saveCallback) // Editable flag was false, but a callback was provided.
    ) {
      console.warn("Editable flag and saveCallback don't jive.");
      
      editable = false;
      saveCallback = null;
  }

  // Toggle for testing.
  // editable = false;
  me.editable = editable;
  
  if(saveCallback) {
    me.saveCallback = saveCallback;
  }
  
  // If not already editable and the user is an admin,
  // tell the template to provide an Edit Raid button.
  var offerEdit = (! editable && dkpoints.auth.checkAuthLevel() == 2)
  
  var body = goog.dom.$('body_content');
  body.innerHTML = dkpoints.raid.templates.verifyForm({editable: editable, offerEdit: offerEdit}, null);
  
  // Get references to the key DOM elements
  var raidDateField = goog.dom.$('raidDateField'),
      raidZoneField = goog.dom.$('raidZoneField'),
      raidNameField = goog.dom.$('raidNameField');
  
  if(! editable) {
    raidDateField.setAttribute("readonly", true);
    raidZoneField.setAttribute("readonly", true);
    raidNameField.setAttribute("readonly", true);
  }
  
  // Set raid info fields.
  var rInfo = raidOb["info"];
  var date = rInfo["date"];
  
  // Webkit doesn't support "2010-01-06T04:45:29Z" formatted dates.
  if(goog.userAgent.ASSUME_GECKO || goog.userAgent.ASSUME_OPERA) {
    date = new Date(date).toLocaleString();
  }
  
  raidDateField.value = date;
  raidZoneField.value = rInfo["zone"];
  raidNameField.value = rInfo["name"];
  
  // Build the Raid Members list
  var raidMembers = goog.dom.$('RaidMembers'),
      rPlayers = raidOb["players"];

  var raidList = me.genRaidMemberList(rPlayers, editable);
  raidMembers.innerHTML = "";
  raidMembers.appendChild(raidList);
  
  // Set up loot display.
  var raidLoot = goog.dom.$('LootList'),
      lootList = me.genLootList(raidOb, editable);
  
  raidLoot.innerHTML = ""
  raidLoot.appendChild(lootList);
  
  // Set up drag and drop only for the editable view.
  if(editable) {
    me.initDragAndDrop();
    me.setupEventHandlers();
  }
  
  me.rendered = true;
};

/**
 * Generates the div with the list of raid members.
 * @param {Array} rPlayers Array of player objects (from the raid object's 'player' attr)
 * @param {boolean} editable Whether to include or not include editing controls
 * @return {!Node}
 */
dkpoints.raid.parsedview.genRaidMemberList = function(rPlayers, editable) {
  /** @type {!Node} */
  var listDocFrag = document.createDocumentFragment();

  var delOffset = 0; // Offset value used to skip deleted players.
  for(var i = 0; i < rPlayers.length; ++i) {  
    var thisPlayer = rPlayers[i];
    
    if(thisPlayer["delete"]) {
      ++delOffset;
    }
    else {
      // Set up params to give to the template.
      var player = {
        number: i + 1 - delOffset,
        index: i,
        playerName: thisPlayer["playerName"],
        playerClass: thisPlayer["playerClass"],
        editable: editable
      };
      
      var playerDoc = soy.renderAsFragment(dkpoints.raid.templates.newRaider, player);
      listDocFrag.appendChild(playerDoc);
    }
  }
  
  if(editable) {
    var addFields = soy.renderAsFragment(dkpoints.raid.templates.addRaider, {});
    listDocFrag.appendChild(addFields);
  }
  
  return listDocFrag;
};

/**
 * Generates the div with the list of loot.
 * @param {!dkpoints.raid.Raid} raidOb The raid object.  Needed to look up player classes.
 * @param {boolean} editable Whether to include or not include editing controls
 * @return {!Node}
 */
dkpoints.raid.parsedview.genLootList = function(raidOb, editable) {
  var me = dkpoints.raid.parsedview;
    
  /** @type {!Node} */
  var lootDocFrag = document.createDocumentFragment();

  // Get a reference to the tierList, see if it exists.  If not, use a dummy array.
  var tierList = dkpoints.raid.tierList;
  tierList = goog.isDefAndNotNull(tierList) ? tierList : [];

  // Get a reference to the tierList, see if it exists.  If not, use a dummy value.
  var instanceTier = me.raid.tier;
  instanceTier = goog.isDef(instanceTier) ? instanceTier : -1;
  
  var rLoot = raidOb["loot"];
  for(var j = 0, len = rLoot.length; j < len; ++j) {
    var thisLoot = rLoot[j],
        player = thisLoot["looter"],
        tier;
    
    if(goog.isDef(thisLoot["tier"])) {
      tier = thisLoot["tier"];
    }
    else {
      tier = instanceTier;
    }
    
    var slot = goog.isDef(thisLoot["slot"]) ? thisLoot["slot"] : "";
    
    var lootDetails = {
      wowheadLink: dkpoints.utils.getWowheadLink(thisLoot["item"]),
      lootIcon: dkpoints.utils.getArmoryIconSrc(thisLoot["icon"]),
      itemQuality: thisLoot["quality"],
      lootName: thisLoot["name"],
      interest: thisLoot["interest"],
      playerName: player,
      playerClass: raidOb.getPlayerClass(player),
      tierList: tierList,
      instanceTier: tier,
      slot: slot,
      
      // Widget set-up options
      index: j,
      editable: editable
    };
    
    var lootFragment = soy.renderAsFragment(dkpoints.raid.templates.newLoot, lootDetails);
    lootDocFrag.appendChild(lootFragment);
  }
  
  // Add the Add Loot panel
  if(editable) {
    var addPanel = soy.renderAsFragment(dkpoints.raid.templates.addLootPanel, {});
    lootDocFrag.appendChild(addPanel);
  }
  
  return lootDocFrag;
};

/**
 * Set up Drag and drop functionality for the raid members => 'Looted by' fields.
 */
dkpoints.raid.parsedview.initDragAndDrop = function() {
  var me = dkpoints.raid.parsedview,
      players = me.raid["players"],
      loots = me.raid["loot"];
  
  var playerGroup = new goog.fx.DragDropGroup(),
      lootGroup = new goog.fx.DragDropGroup();
  
  // Set up "<name>Lbl" elements as draggables.
  for(var i = 0, len = players.length; i < len; ++i) {
   var player = players[i]["playerName"];
   
   if(! goog.isDef(player["del"])) {
    playerGroup.addItem(player + "Lbl");
   }
  }
  
  // Set up "looter#" elements as drag targets.
  var delOffset = 0; // Offset index to skip deleted loot objects in raid["loot"].
  for(i = 0, len = loots.length; i < len; ++i) {
    var loot = loots[i];
    
    if(! goog.isDef(loot["del"])) {
      var lootEl = goog.dom.$('looter' + (i - delOffset));
      lootGroup.addItem(lootEl);
    }
    else { // Object is marked for deletion, skip it, but increment the array index offset.
      ++delOffset;
    }
  }
  
  playerGroup.addTarget(lootGroup);
  playerGroup.init();
  lootGroup.init();
  
  // Set up the listener on the drop targets.
  goog.events.listen(lootGroup, goog.fx.AbstractDragDrop.EventType.DROP, me.dropName, false, lootGroup)
  
  me.playerGroup = playerGroup;
  me.looterGroup = lootGroup;
};

/**
 * Unhook all drag & drop event listeners
 */
dkpoints.raid.parsedview.unhookDDListeners = function() {
  var me = dkpoints.raid.parsedview;
  
  var looterGroup = me.looterGroup;
  goog.events.unlisten(looterGroup, goog.fx.AbstractDragDrop.EventType.DROP, me.dropName, false, looterGroup);
  
  looterGroup.removeItems();
  me.playerGroup.removeItems();
  
  me.looterGroup = looterGroup = null;
  me.playerGroup = null;
};

/**
 * Handle a DROP event, where the user dropped a player's name onto a 'Looted by' field
 * @param {Object} event The DROP event.
 */
dkpoints.raid.parsedview.dropName = function(event) {
  var me = dkpoints.raid.parsedview,
      target = event.dropTargetItem.element,
      src = event.dragSourceItem.element;
    
  var name = goog.dom.getTextContent(src);
  target.value = name;
  
  var newClass = me.raid.getPlayerClass(name);
  goog.dom.classes.set(target, newClass);
  
  var id = target.id,
      index,
      indexRE = /^\S*(\d+)$/;
  if(indexRE.test(id)) {
    index = indexRE.exec(id)[1];
  }
  
  me.raid["loot"][index]["looter"] = name;
};

/**
 * Callback for the GetTierList command object.  When we get the tier list back,
 * do something with it.
 * @param {!Object} tierDetails An object containing the tier list
 */
dkpoints.raid.parsedview.gotTierList = function(tierDetails) {
  var start = Date.now();
  var me = dkpoints.raid.parsedview,
      raid = me.raid,
      loots = raid["loot"],
      tier;
  
  dkpoints.raid.tierList = tierDetails.tierList;
  tier = parseFloat(tierDetails.instanceTier);
  raid.setTier(tier);

  if(me.rendered) {
    var selects = goog.dom.$$("select", "tierSelect"),
        tierListHTML = dkpoints.raid.templates.buildTierSelect(tierDetails, null);
    
    for(var i = 0, len = selects.length; i < len; ++i) {
      var thisSelect = selects[i],
          thisLoot = loots[i];
      
      // Set up the dropdown boxes.
      thisSelect.innerHTML = tierListHTML;
      
      // If the raid loot object's tier is different than instanceTier, override instanceTier.
      if(thisLoot["tier"] != tier) {
        for(var j = thisSelect.options.length; j--; ) {
          var thisOption = thisSelect.options[j];
          if(parseFloat(thisOption.value) == tier) {
            thisOption.selected = true;
          }
        }
      }
    }
  }
  dkpoints.status.addMessage("gotTierList executed in " + (Date.now() - start) + "ms.", "gotTierList", "Raid Editor", 10000);
};

/**
 * This method attaches event handlers to the top-level page elements (Raid Members list, Raid Loot list)
 * Events are allowed to bubble up to these top-level items and are dealt with there.
 * This allows us to avoid attaching a huge number of event handlers on the page.
 */
dkpoints.raid.parsedview.setupEventHandlers = function() {
  var me = dkpoints.raid.parsedview,
      memberList = goog.dom.$('RaidMembers'),
      lootList = goog.dom.$('LootList');
  
  // Set up handlers for the Raid Member list.
  goog.events.listen(memberList, goog.events.EventType.CLICK, me.handleRaidMemberAction, false, me);
  
  // Set up handlers for the Loot list.
  goog.events.listen(lootList, goog.events.EventType.CLICK, me.handleLootListAction, false, me);
  goog.events.listen(lootList, goog.events.EventType.CHANGE, me.handleLootListAction, false, me);
  
  // Set up the keypress handler for the Item Name box.
  goog.events.listen(lootList, goog.events.EventType.KEYUP, me.handleItemNameUpdate, false, me);
};

/**
 * Handle events in the Raid Member list.
 * @param {goog.events.Event} event The event to handle.
 */
dkpoints.raid.parsedview.handleRaidMemberAction = function(event) {
  var me = dkpoints.raid.parsedview,
      status = dkpoints.status,
      
      raid = me.raid,
      raidMembers = raid["players"];
  
  var target = /** @type {!Element} */ event.target,
      targetID = target.id;
  
  /* There are only two actions to be handled in the Raid Member list:
   * Adding a member, and removing a member.
   */
  
  var dirty = false; // Track if any changes are made.
  if(targetID == "AddPlayer") {
    var playerToAdd = goog.dom.$('NewPlayer').value;
    
    // If name includes whitespace, numbers, or is empty, don't add.
    if(! (playerToAdd.match(/\s/) || playerToAdd.match(/\d/) || playerToAdd.length == 0) ) {
      // Make sure this player isn't in the raid already.
      var inRaid = false;
      for(var i = 0, len = raidMembers.length; i < len; ++i) {
        if(raidMembers[i]["playerName"] == playerToAdd) {
          inRaid = true;
          break;
        }
      }
      
      if(! inRaid) {
        // Add him/her to the raid.players array.
        raidMembers.push(
          {
            "playerName": playerToAdd,
            "playerClass": "noclass",
            "dirty": true // Mark this as being new/updated
          }
        );
        
        status.addMessage("Player added to raid: " + playerToAdd, "PlayerInRaid", "Raid Editor", 5000);
        dirty = true;
      }
      else {
        status.addMessage("Player already in raid: " + playerToAdd, "PlayerInRaid", "Raid Editor", 5000);
      }
    }
    else {
      status.addMessage("Invalid player name: " + playerToAdd, "InvalidPlayerName", "Raid Editor", 5000);
    }
  }
  else {
    var playerRE = /^(\S+)Rmv$/;
    if(playerRE.test(targetID)) { // Trying to remove a player.
      // Get the player's name from the ID.
      var playerToRemove = playerRE.exec(targetID)[1];
      
      // Make sure this player isn't in the raid already.
      var removed = false;
      for(var i = 0, len = raidMembers.length; i < len; ++i) {
        if(raidMembers[i]["playerName"] == playerToRemove) {
          // If it matches, remove the player
          raidMembers.splice(i, 1);
          
          dirty = true;
          removed = true;
          break;
        }
      }
      
      if(removed) {
        status.addMessage("Player removed from the raid: " + playerToRemove, playerToRemove + "Removed", "Raid Editor", 5000);
      }
      else {
        status.addMessage("Player could not be removed: " + playerToRemove, playerToRemove + "NotRemoved", "Raid Editor", 5000);
      }
    }
  }// else: User clicked something we don't care about.
  
  // A change was made to the raid members array
  if(dirty) {
    // Mark raid as changed.
    raid["info"]["dirty"] = true;
    
    // Tear down the drag-and-drop event listeners.
    me.unhookDDListeners();
    
    // Rewrite Raid Members list.
    var memberList = goog.dom.$('RaidMembers');
    var newMemberList = me.genRaidMemberList(raidMembers, true);
    
    var start = Date.now();
    memberList.innerHTML = "";
    memberList.appendChild(newMemberList);
    
    // Reinitialize drag-and-drop event listeners.
    me.initDragAndDrop();
  }
};

/**
 * Handle events in the Raid Loot list.
 * @param {goog.events.Event} event The event to handle.
 */
dkpoints.raid.parsedview.handleLootListAction = function(event) {
  var me = dkpoints.raid.parsedview,
      raid = me.raid;
  /* These are the events we're interested in:
   *  Changing the Tier selct box
   *  Changing the Tier input field (normally hidden)
   *  Changing the Slot select box
   *  Changing the looter
   *  Changing one of the Interest level radio buttons.
   *  Changing the Item Name in the Add Loot panel
   *  Clicking the Remove button.
   *  Clicking the Add/Search button in the Add Loot panel.
   */
  
  var target = /** @type {Node} */ (event.target);
  var targetID = target.id;
  
  if(event.type == 'click') {
    // Only two click events we're interested in.
    
    // User is trying to add a piece of loot.
    if(targetID == 'addLootBtn') {
      me.searchItem( goog.dom.$('addLootName') );
    }
    else { // User clicked one of the Remove buttons.
      var lootRemRE = /^lootRemove(\d+)$/;
      if(lootRemRE.test(targetID)) {
        // If one of the Remove buttons was pressed, remove the loot item.
        var indexToDelete = parseInt(lootRemRE.exec(targetID)[1], 10);
        
        var lootName = raid["loot"][indexToDelete]["name"];        
        
        raid["info"]["dirty"] = true;
        raid["loot"].splice(indexToDelete, 1);
        
        // Tear down the drag-and-drop event listeners.
        me.unhookDDListeners();
        
        // Rewrite Raid Members list.
        var lootList = goog.dom.$('LootList'),
            newLootList = me.genLootList(raid, true);
        
        var start = Date.now();
        lootList.innerHTML = "";
        lootList.appendChild(newLootList);
        
        dkpoints.status.addMessage("Item removed from raid: " + lootName, lootName + "Removed", "Raid Editor", 10000);
        
        // Reinitialize drag-and-drop event listeners.
        me.initDragAndDrop();
      }
    }
  }
  else { // event.type != 'change'
    // All controls are IDed with a numerical index that matches that loot
    // to an item with that index in the raid["loot"] array.
    
    // Get the control name and index.
    // Properly formatted xxxxxxxx#
    var controlRE = /^(\S*)(\d+)$/;
    if(controlRE.test(targetID)) {
      var matches = controlRE.exec(targetID),
          controlName = matches[1],
          index = matches[2];
      
      var lootItem = raid["loot"][index];
      
      switch (controlName) {
        // User changed the tier dropdown for loot {index}.
        case "lootTierSelect":
          var newTier = target.value;
          
          // Somehow this raid was for a tier that isn't in the tier list.
          if(newTier == "Other") {
            // Hide the select, show the text box.
            target.style.display = "none";
            
            var tierTextBox = goog.dom.$('lootTierInput' + index);
            tierTextBox.style.display = "inline";
            
            if(tierTextBox.value == "") {
              tierTextBox.value = "Enter tier...";
            }
            tierTextBox.select();
            lootItem["tier"] = undefined;
          }
          else {
            // User selected one of the options.
            if(newTier != "Choose...") {
              // Save the selected option on the raid's loot objet.
              lootItem["tier"] = parseFloat(newTier);
            }
            else { // User selected "Choose..."
              // Clear the tier property on this loot item
              thisLoot["tier"] = undefined;
            }
          }
          break; // end case "lootTierSelect"
        
        // User changed the text in the tier input box.
        case "lootTierInput":
          
          // If the user-entered value matches one of the options in the Select box,
          // show the select, pick that option, and hide the input box.
          var select = goog.dom.$('lootTierSelect' + index),
              options = select.options;
              newTier = parseFloat(target.value);
          
          for(var i = options.length; i--; ) {
            var thisOption = options[i]
            if(parseFloat(thisOption.value) === newTier) {
              // Select this option
              thisOption.selected = true;
              
              // Toggle which control is visible.
              target.style.display = "none";
              select.style.display = "inline";
              break; // Test breaking out of the loop, but not the switch
            }
          }
          
          // Regardless of whether it was found in the select box or not,
          // set the new tier to the loot object's tier property
          lootItem["tier"] = newTier;
          break; // end case "lootTierInput"
        
        // User picked a slot
        case "slotSelect":
          var newSlot = target.value;
          
          if(newSlot == "" || newSlot == "Select one...") {
            newSlot = undefined;
          }
          
          lootItem["slot"] = newSlot;
          break; // end case "slotSelect":
        
        // User changed the looter of an item
        case "looter":
          var newLooter = target.value;
          var newClass = raid.getPlayerClass(newLooter);
          
          if(newClass == "noclass") {
            dkpoints.status.addMessage('Player "' + newLooter + '" is not a raid member.', newLooter + "NotRaider", "Raid Editor", 5000);
          }
          
          goog.dom.classes.set(target, newClass);
          lootItem["looter"] = newLooter;
          break; // end case "looter"
        
        /* If the interest level is On-spec or Off-spec, show the "Looted by:" field.
          Otherwise, hide it.
        */
        case "lootOnspec":
          target.parentNode.previousSibling.style.visibility = "visible";
          lootItem["interest"] = "Onspec";
          break; // end case "lootOnspec":
        case "lootOffspec":
          target.parentNode.previousSibling.style.visibility = "visible";
          lootItem["interest"] = "Offspec";
          break; // end case "lootOffspec":
        case "lootBanked":
          target.parentNode.previousSibling.style.visibility = "hidden";
          lootItem["interest"] = "Banked";
          break; // end case "lootBanked":
        case "lootDEed":
          target.parentNode.previousSibling.style.visibility = "hidden";
          lootItem["interest"] = "Disenchanted";
          break; // end case "lootDEed":
        // default:  We don't care about anything else.
      }
      
      lootItem["dirty"] = raid["info"]["dirty"] = true;
    } // else: targetID didn't match the format we're interested in.
  }
};

/**
 * Keypress handler for the Item Name input box.
 * @param {goog.events.Event} event The click event.
 */
dkpoints.raid.parsedview.handleItemNameUpdate = function(event) {
  /*  This is actually attached to the Loot List pane,
      so first check to see that the Item Name box was the target.
  */
  
  var target = event.target;
  if(target.id == "addLootName") {
    var itemName = target.value;
    console.log("Look for: " + itemName);
    
    var cmdGetItemList = new dkpoints.rpc.commands.GetItemList(me.searchItem, 7);
  }
  // else: If it wasn't the Item Name box, we don't care.
};

/**
 * Initiate a search for the item
 * @param {string} itemName The name of the item to look for
 */
dkpoints.raid.parsedview.searchItem = function(itemName) {
  
};

/**
 * Tear down the raid view/edit UI.
 */
dkpoints.raid.parsedview.teardown = function() {
  var me = dkpoints.raid.parsedview;

  // Unhook handlers for the Raid Member and loot lists.
  var memberList = goog.dom.$('RaidMembers');
  var lootList = goog.dom.$('LootList');
  
  if(me.editable) {
    me.unhookDDListeners();

    // Unhook handlers for the Raid Member & Loot lists.
    goog.events.unlisten(memberList, goog.events.EventType.CLICK, me.handleRaidMemberAction, false, me);
    
    goog.events.unlisten(lootList, goog.events.EventType.CLICK, me.handleLootListAction, false, me);
    goog.events.unlisten(lootList, goog.events.EventType.CHANGE, me.handleLootListAction, false, me);
    goog.events.unlisten(lootList, goog.events.EventType.KEYPRESS, me.handleItemNameUpdate, false, me);
  }
  
  // Delete references.
  // me.raid = null; Don't dereference the raid.  Doing so will break the back button.
  me.saveCallback = null;
  
  // Clear DOM
  goog.dom.$('body_content').innerHTML = "";
};